﻿namespace BrowserCacheBustingUtilities
{
    using System;
    using System.Collections.Generic;
    using System.Configuration;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;
    using System.Text;

    public static class CacheBuster
    {

        #region Enums

        public enum CommandType
        {
            CacheBust = 0,
            GetUniqueFileExtensions = 1,
            Cleanup = 2,
            CleanupButKeepNonVersioned = 3
        }

        public enum PathType
        {
            Directory = 0,
            File = 1,
            Extension = 2
        }

        public enum Filter
        {
            Exclude = 0,
            Include = 1
        }

        public enum CacheBustType
        {
            Hash = 0,
            Version
        }

        #endregion Enums

        #region Members and constants

        private const CommandType DefaultCommandType = CommandType.CacheBust;
        private const CacheBustType DefaultCacheBustType = CacheBustType.Hash;
        public const Filter DefaultFilter = Filter.Exclude;
        private static IOutput _output = new ConsoleOutput();
        private static Stopwatch _stopwatch;
        private static List<string> _assetFilesDeleted = new List<string>();
        private static List<string> _assetFilesRenamed = new List<string>();
        private static List<string> _sourceFilesUpdated = new List<string>();

        #endregion Members and constants

        #region Properties

        public static DirectoryInfo ApplicationPath { get; set; }
        public static CommandType Command { get; internal set; }
        public static CacheBustType CacheBustingType { get; internal set; }

        public static List<string> AssetFolderPaths { get; internal set; }
        public static List<string> ExcludedAssetFolderPaths { get; internal set; }

        public static string AssetRootPath { get; internal set; }
        public static string SourceRootPath { get; internal set; }
        public static List<string> SourceFolderPaths { get; internal set; }
        public static List<string> ExcludedSourceFolderPaths { get; internal set; }

        public static List<string> AssetFolderNamesFilter { get; internal set; }
        public static Filter AssetFolderNamesFilterType { get; internal set; }
        public static List<string> AssetFilePathsFilter { get; internal set; }
        public static Filter AssetFilePathsFilterType { get; internal set; }
        public static List<string> AssetFileNamesFilter { get; internal set; }
        public static Filter AssetFileNamesFilterType { get; internal set; }
        public static List<string> AssetFileExtensionsFilter { get; internal set; }
        public static Filter AssetFileExtensionsFilterType { get; internal set; }

        public static List<string> SourceFolderNamesFilter { get; internal set; }
        public static Filter SourceFolderNamesFilterType { get; internal set; }
        public static List<string> SourceFilePathsFilter { get; internal set; }
        public static Filter SourceFilePathsFilterType { get; internal set; }
        public static List<string> SourceFileNamesFilter { get; internal set; }
        public static Filter SourceFileNamesFilterType { get; internal set; }
        public static List<string> SourceFileExtensionsFilter { get; internal set; }
        public static Filter SourceFileExtensionsFilterType { get; internal set; }

        public static List<string> IncludedDomainNames { get; internal set; }
        public static bool DeleteOldFileVersions { get; internal set; }
        public static string OutputFilePath { get; internal set; }
        public static string VersionFilePath { get; internal set; }
        public static string MD5HashPrefix { get; internal set; }
        public static int NumberOfVersionsToKeep { get; internal set; }

        internal static bool OutputExclusionsToConsole { get; set; }

        internal static List<Asset> AssetFiles = new List<Asset>();
        internal static List<Asset> SourceFiles = new List<Asset>();

        static CacheBuster()
        {
            OutputExclusionsToConsole = false;
        }

        public static IOutput Output
        {
            get
            {
                return _output;
            }
            set
            {
                _output = value;
            }
        }

        #endregion Properties

        #region Public methods

        public static List<string> ProcessCommandLineArguments(string[] arguments)
        {
            ResetArguments();

            if (arguments.Length == 1)
            {
                string argument = string.Concat(" ", arguments[0].Trim());

                List<string> argumentPrefixes = new List<string>
                                                    {
                                                        "-c:",
                                                        "-m:",
                                                        "-arfp:",
                                                        "-srfp:",
                                                        "-afp:",
                                                        "-eafp:",
                                                        "-iafp:",
                                                        "-sfp:",
                                                        "-esfp:",
                                                        "-isfp:",
                                                        "-i:",
                                                        "-d:",
                                                        "-afonf:",
                                                        "-afonft:",
                                                        "-afpf:",
                                                        "-afpft:",
                                                        "-afnf:",
                                                        "-afnft:",
                                                        "-afef:",
                                                        "-afeft:",
                                                        "-sfonf:",
                                                        "-sfonft:",
                                                        "-sfpf:",
                                                        "-sfpft:",
                                                        "-sfnf:",
                                                        "-sfnft:",
                                                        "-sfef:",
                                                        "-sfeft:",
                                                        "-o:",
                                                        "-v:",
                                                        "-hp:",
                                                        "-k:"
                                                    };

                int suppliedArguments = argumentPrefixes.Count(prefix => argument.Contains(string.Concat(" ", prefix)));

                if (suppliedArguments > 1)
                {
                    arguments = argument.Trim().Split(new[] { "-" }, StringSplitOptions.RemoveEmptyEntries);

                    for (int i = 0; i < arguments.Length; i++)
                        arguments[i] = string.Concat("-", arguments[i]);
                }
            }

            foreach (string argument in arguments.Select(argument => argument.Trim()))
            {
                if (argument.StartsWith("-c:"))
                {
                    CommandType parsedCommandType;

                    if (Enum.TryParse(argument.Substring(3), out parsedCommandType))
                        Command = parsedCommandType;
                }

                if (argument.StartsWith("-m:"))
                {
                    CacheBustType parsedCacheBustType;

                    if (Enum.TryParse(argument.Substring(3), out parsedCacheBustType))
                        CacheBustingType = parsedCacheBustType;
                }

                if (argument.StartsWith("-arfp:"))
                    AssetRootPath = ProcessFilePathArguments(argument.Substring(6).Trim(), PathType.Directory)[0];

                if (argument.StartsWith("-srfp:"))
                    SourceRootPath = ProcessFilePathArguments(argument.Substring(6).Trim(), PathType.Directory)[0];

                if (argument.StartsWith("-afp:"))
                    AssetFolderPaths = ProcessFilePathArguments(argument.Substring(5), PathType.Directory);

                if (argument.StartsWith("-eafp:"))
                    ExcludedAssetFolderPaths = ProcessFilePathArguments(argument.Substring(6), PathType.Directory);

                if (argument.StartsWith("-sfp:"))
                    SourceFolderPaths = ProcessFilePathArguments(argument.Substring(5), PathType.Directory);

                if (argument.StartsWith("-esfp:"))
                    ExcludedSourceFolderPaths = ProcessFilePathArguments(argument.Substring(6), PathType.Directory);

                if (argument.StartsWith("-afonf:"))
                    AssetFolderNamesFilter = ProcessFilePathArguments(argument.Substring(7));

                if (argument.StartsWith("-afonft:"))
                {
                    Filter parsedFilter;

                    if (Enum.TryParse(argument.Substring(8), out parsedFilter))
                        AssetFolderNamesFilterType = parsedFilter;
                }

                if (argument.StartsWith("-afpf:"))
                    AssetFilePathsFilter = ProcessFilePathArguments(argument.Substring(6));

                if (argument.StartsWith("-afpft:"))
                {
                    Filter parsedFilter;

                    if (Enum.TryParse(argument.Substring(7), out parsedFilter))
                        AssetFilePathsFilterType = parsedFilter;
                }

                if (argument.StartsWith("-afnf:"))
                    AssetFileNamesFilter = ProcessFilePathArguments(argument.Substring(6));

                if (argument.StartsWith("-afnft:"))
                {
                    Filter parsedFilter;

                    if (Enum.TryParse(argument.Substring(7), out parsedFilter))
                        AssetFileNamesFilterType = parsedFilter;
                }

                if (argument.StartsWith("-afef:"))
                    AssetFileExtensionsFilter = ProcessFilePathArguments(argument.Substring(6), PathType.Extension);

                if (argument.StartsWith("-afeft:"))
                {
                    Filter parsedFilter;

                    if (Enum.TryParse(argument.Substring(7), out parsedFilter))
                        AssetFileExtensionsFilterType = parsedFilter;
                }

                if (argument.StartsWith("-sfonf:"))
                    SourceFolderNamesFilter = ProcessFilePathArguments(argument.Substring(7));

                if (argument.StartsWith("-sfonft:"))
                {
                    Filter parsedFilter;

                    if (Enum.TryParse(argument.Substring(8), out parsedFilter))
                        SourceFolderNamesFilterType = parsedFilter;
                }

                if (argument.StartsWith("-sfpf:"))
                    SourceFilePathsFilter = ProcessFilePathArguments(argument.Substring(6));

                if (argument.StartsWith("-sfpft:"))
                {
                    Filter parsedFilter;

                    if (Enum.TryParse(argument.Substring(7), out parsedFilter))
                        SourceFilePathsFilterType = parsedFilter;
                }

                if (argument.StartsWith("-sfnf:"))
                    SourceFileNamesFilter = ProcessFilePathArguments(argument.Substring(6));

                if (argument.StartsWith("-sfnft:"))
                {
                    Filter parsedFilter;

                    if (Enum.TryParse(argument.Substring(7), out parsedFilter))
                        SourceFileNamesFilterType = parsedFilter;
                }

                if (argument.StartsWith("-sfef:"))
                    SourceFileExtensionsFilter = ProcessFilePathArguments(argument.Substring(6), PathType.Extension);

                if (argument.StartsWith("-sfeft:"))
                {
                    Filter parsedFilter;

                    if (Enum.TryParse(argument.Substring(7), out parsedFilter))
                        SourceFileExtensionsFilterType = parsedFilter;
                }

                if (argument.StartsWith("-i:"))
                    IncludedDomainNames = ProcessFilePathArguments(argument.Substring(3));

                if (argument.StartsWith("-d:"))
                    DeleteOldFileVersions = argument.Substring(3).Trim() == "1";

                if (argument.StartsWith("-o:"))
                    OutputFilePath = argument.Substring(3).Trim();

                if (argument.StartsWith("-v:"))
                    VersionFilePath = argument.Substring(3).Trim();

                if (argument.StartsWith("-hp:"))
                    MD5HashPrefix = argument.Substring(4).Trim();

                if (argument.StartsWith("-k:"))
                {
                    int parsedInt;

                    if (int.TryParse(argument.Substring(3).Trim(), out parsedInt) && parsedInt > 0)
                        NumberOfVersionsToKeep = parsedInt;
                }
            }

            UpdatePaths(AssetFolderPaths);
            UpdatePaths(SourceFolderPaths);
            UpdatePaths(ExcludedAssetFolderPaths);
            UpdatePaths(ExcludedSourceFolderPaths);
            UpdatePaths(AssetFilePathsFilter);
            UpdatePaths(SourceFilePathsFilter);

            return ValidateArguments();
        }

        public static string GetConsoleArguments()
        {
            StringBuilder arguments = new StringBuilder();

            arguments.AppendLine(@"Example usage: BrowserCacheBusting.exe -afp:c:\dev\web\assets -srfp:c:\dev\web -sfp:c:\dev\web -eafe:swf,fla -esfp:c:\dev\web\bin");
            arguments.AppendLine();
            arguments.AppendLine("   -c:      0 = Cache Bust (DEFAULT)");
            arguments.AppendLine("            1 = Get unique asset and source file extensions");
            arguments.AppendLine("            2 = Clean up old assets");
            arguments.AppendLine("            3 = Clean up old assets but keep original assets (non hashed/versioned)");
            arguments.AppendLine("   -m:      0 = Cache bust using MD5 hash (DEFAULT)");
            arguments.AppendLine("            1 = Cache bust using version number");
            arguments.AppendLine("   -afp:    Asset folder paths (REQUIRED, comma delimited)");
            arguments.AppendLine("   -sfp:    Source folder paths (REQUIRED if -c: is 1 or 2, comma delimited)");
            arguments.AppendLine("   -srfp:   Source root folder path (REQUIRED if -c: is 1 or 2, comma delimited)");
            arguments.AppendLine("   -arfp:   Asset root folder path");
            arguments.AppendLine("   -eafp:   Excluded asset folder paths (comma delimited)");
            arguments.AppendLine("   -esfp:   Excluded source folder paths (comma delimited)");
            arguments.AppendLine("   -afonf:  Asset folder names filter (comma delimited)");
            arguments.AppendLine("   -afonft: 0 = Asset folder names filter type - Exclude (DEFAULT)");
            arguments.AppendLine("            1 = Asset folder names filter type - Include");
            arguments.AppendLine("   -afpf:   Asset File paths filter (comma delimited)");
            arguments.AppendLine("   -afpft:  0 = Asset File paths filter type - Exclude (DEFAULT)");
            arguments.AppendLine("            1 = Asset File paths filter type - Include");
            arguments.AppendLine("   -afnf:   Asset File names filter (comma delimited)");
            arguments.AppendLine("   -afnft:  0 = Asset File names filter type - Exclude (DEFAULT)");
            arguments.AppendLine("            1 = Asset File names filter type - Include");
            arguments.AppendLine("   -afef:   Asset File extensions filter (comma delimited)");
            arguments.AppendLine("   -afeft:  0 = Asset File extensions filter type - Exclude (DEFAULT)");
            arguments.AppendLine("            1 = Asset File extensions filter type - Include");
            arguments.AppendLine("   -sfonf:  Source folder names filter (comma delimited)");
            arguments.AppendLine("   -sfonft: 0 = Source folder names filter type - Exclude (DEFAULT)");
            arguments.AppendLine("            1 = Source folder names filter type - Include");
            arguments.AppendLine("   -sfpf:   Source File paths filter (comma delimited)");
            arguments.AppendLine("   -sfpft:  0 = Source File paths filter type - Exclude (DEFAULT)");
            arguments.AppendLine("            1 = Source File paths filter type - Include");
            arguments.AppendLine("   -sfnf:   Source File names filter (comma delimited)");
            arguments.AppendLine("   -sfnft:  0 = Source File names filter type - Exclude (DEFAULT)");
            arguments.AppendLine("            1 = Source File names filter type - Include");
            arguments.AppendLine("   -sfef:   Source File extensions filter (comma delimited)");
            arguments.AppendLine("   -sfeft:  0 = Source File extensions filter type - Exclude (DEFAULT)");
            arguments.AppendLine("            1 = Source File extensions filter type - Include");
            arguments.AppendLine("   -d:      0 = Keep old asset file versions (DEFAULT)");
            arguments.AppendLine("            1 = Keep old asset file versions");
            arguments.AppendLine("   -i:      Included domain names (comma delimited)");
            arguments.AppendLine("   -o:      Output file path");
            arguments.AppendLine("   -v:      Version file path");
            arguments.AppendLine("   -hp:     MD5 Hash Prefix");
            arguments.AppendLine("   -k:      Number of versions to keep (DEFAULT is 1)");

            arguments.AppendLine();
            return arguments.ToString();
        }

        public static void Go()
        {
            VersionManager.Loaded = false;
            FileManager.Output = Output;
            List<string> errors = ValidateArguments();

            if (errors.Count > 0)
            {
                foreach (string error in errors)
                    Output.WriteLine(error);

                WriteToOutputFile();
                return;
            }

            if (Command == CommandType.CacheBust)
                CacheBust();
            else if (Command == CommandType.GetUniqueFileExtensions)
                AnalyzeUniqueFileExtensions();
            else if (Command == CommandType.Cleanup || Command == CommandType.CleanupButKeepNonVersioned)
                CleanUp();

            WriteToOutputFile();
        }

        #endregion Public methods

        #region Internal/Private methods

        private static void WriteToOutputFile()
        {
            if (string.IsNullOrWhiteSpace(OutputFilePath))
                return;

            File.WriteAllText(OutputFilePath, Output.Data.ToString());
        }

        private static void UpdatePaths(List<string> paths)
        {
            for (int i = 0; i < paths.Count; i++)
            {
                string path = paths[i];

                if (!string.IsNullOrWhiteSpace(AssetRootPath))
                {
                    path = path.Replace("%AssetRootPath%\\", AssetRootPath);
                    path = path.Replace("%AssetRootPath%", AssetRootPath);
                }

                if (!string.IsNullOrWhiteSpace(SourceRootPath))
                {
                    path = path.Replace("%SourceRootPath%\\", SourceRootPath);
                    path = path.Replace("%SourceRootPath%", SourceRootPath);
                }

                paths[i] = path;
            }
        }

        internal static void ResetArguments()
        {
            Command = DefaultCommandType; // -c
            CacheBustingType = DefaultCacheBustType; //-m

            AssetRootPath = string.Empty; // -arfp
            SourceRootPath = string.Empty; // -srfp
            AssetFolderPaths = new List<string>(); // -afp
            ExcludedAssetFolderPaths = new List<string>(); // -eafp
            SourceFolderPaths = new List<string>(); // -sfp
            ExcludedSourceFolderPaths = new List<string>(); // -esfp

            AssetFolderNamesFilter = new List<string>(); // -afonf
            AssetFolderNamesFilterType = DefaultFilter; // -afonft
            AssetFilePathsFilter = new List<string>(); // -afpf
            AssetFilePathsFilterType = DefaultFilter; // -afpft
            AssetFileNamesFilter = new List<string>(); // -afnf
            AssetFileNamesFilterType = DefaultFilter; // -afnft
            AssetFileExtensionsFilter = new List<string>(); // -afef
            AssetFileExtensionsFilterType = DefaultFilter; // -afeft

            SourceFolderNamesFilter = new List<string>(); // -sfonf
            SourceFolderNamesFilterType = DefaultFilter; // -sfonft
            SourceFilePathsFilter = new List<string>(); // -sfpf
            SourceFilePathsFilterType = DefaultFilter; // -sfpft
            SourceFileNamesFilter = new List<string>(); // -sfnf
            SourceFileNamesFilterType = DefaultFilter; // -sfnft
            SourceFileExtensionsFilter = new List<string>(); // -sfef
            SourceFileExtensionsFilterType = DefaultFilter; // -sfeft

            IncludedDomainNames = new List<string>(); // i
            DeleteOldFileVersions = false; // -d
            OutputFilePath = string.Empty; // -o
            VersionFilePath = string.Empty; // -v
            MD5HashPrefix = string.Empty; // -hp
            NumberOfVersionsToKeep = 1; // -k

            // now take from appsettings if defined
            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["Command"]))
            {
                CommandType parsedCommandType;

                if (Enum.TryParse(ConfigurationManager.AppSettings["Command"], out parsedCommandType))
                    Command = parsedCommandType;
            }

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["CacheBustType"]))
            {
                CacheBustType parsedCacheBustType;

                if (Enum.TryParse(ConfigurationManager.AppSettings["CacheBustType"], out parsedCacheBustType))
                    CacheBustingType = parsedCacheBustType;
            }

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["AssetRootPath"]))
                AssetRootPath = ProcessFilePathArguments(ConfigurationManager.AppSettings["AssetRootPath"].Trim(), PathType.Directory)[0];

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["SourceRootPath"]))
                SourceRootPath = ProcessFilePathArguments(ConfigurationManager.AppSettings["SourceRootPath"].Trim(), PathType.Directory)[0];

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["AssetFolderPaths"]))
                AssetFolderPaths = ProcessFilePathArguments(ConfigurationManager.AppSettings["AssetFolderPaths"].Trim(), PathType.Directory);

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["ExcludedAssetFolderPaths"]))
                ExcludedAssetFolderPaths = ProcessFilePathArguments(ConfigurationManager.AppSettings["ExcludedAssetFolderPaths"].Trim(), PathType.Directory);

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["SourceFolderPaths"]))
                SourceFolderPaths = ProcessFilePathArguments(ConfigurationManager.AppSettings["SourceFolderPaths"].Trim(), PathType.Directory);

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["ExcludedSourceFolderPaths"]))
                ExcludedSourceFolderPaths = ProcessFilePathArguments(ConfigurationManager.AppSettings["ExcludedSourceFolderPaths"].Trim(), PathType.Directory);

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["AssetFolderNamesFilter"]))
                AssetFolderNamesFilter = ProcessFilePathArguments(ConfigurationManager.AppSettings["AssetFolderNamesFilter"].Trim(), PathType.File);

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["AssetFolderNamesFilterType"]))
            {
                Filter parsedFilter;

                if (Enum.TryParse(ConfigurationManager.AppSettings["AssetFolderNamesFilterType"].Trim(), out parsedFilter))
                    AssetFolderNamesFilterType = parsedFilter;
            }

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["AssetFilePathsFilter"]))
                AssetFilePathsFilter = ProcessFilePathArguments(ConfigurationManager.AppSettings["AssetFilePathsFilter"].Trim(), PathType.File);

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["AssetFilePathsFilterType"]))
            {
                Filter parsedFilter;

                if (Enum.TryParse(ConfigurationManager.AppSettings["AssetFilePathsFilterType"].Trim(), out parsedFilter))
                    AssetFilePathsFilterType = parsedFilter;
            }

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["AssetFileNamesFilter"]))
                AssetFileNamesFilter = ProcessFilePathArguments(ConfigurationManager.AppSettings["AssetFileNamesFilter"].Trim(), PathType.File);

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["AssetFileNamesFilterType"]))
            {
                Filter parsedFilter;

                if (Enum.TryParse(ConfigurationManager.AppSettings["AssetFileNamesFilterType"].Trim(), out parsedFilter))
                    AssetFileNamesFilterType = parsedFilter;
            }

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["AssetFileExtensionsFilter"]))
                AssetFileExtensionsFilter = ProcessFilePathArguments(ConfigurationManager.AppSettings["AssetFileExtensionsFilter"].Trim(), PathType.Extension);

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["AssetFileExtensionsFilterType"]))
            {
                Filter parsedFilter;

                if (Enum.TryParse(ConfigurationManager.AppSettings["AssetFileExtensionsFilterType"].Trim(), out parsedFilter))
                    AssetFileExtensionsFilterType = parsedFilter;
            }

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["SourceFolderNamesFilter"]))
                SourceFolderNamesFilter = ProcessFilePathArguments(ConfigurationManager.AppSettings["SourceFolderNamesFilter"].Trim(), PathType.File);

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["SourceFolderNamesFilterType"]))
            {
                Filter parsedFilter;

                if (Enum.TryParse(ConfigurationManager.AppSettings["SourceFolderNamesFilterType"].Trim(), out parsedFilter))
                    SourceFolderNamesFilterType = parsedFilter;
            }

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["SourceFilePathsFilter"]))
                SourceFilePathsFilter = ProcessFilePathArguments(ConfigurationManager.AppSettings["SourceFilePathsFilter"].Trim(), PathType.File);

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["SourceFilePathsFilterType"]))
            {
                Filter parsedFilter;

                if (Enum.TryParse(ConfigurationManager.AppSettings["SourceFilePathsFilterType"].Trim(), out parsedFilter))
                    SourceFilePathsFilterType = parsedFilter;
            }

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["SourceFileNamesFilter"]))
                SourceFileNamesFilter = ProcessFilePathArguments(ConfigurationManager.AppSettings["SourceFileNamesFilter"].Trim(), PathType.File);

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["SourceFileNamesFilterType"]))
            {
                Filter parsedFilter;

                if (Enum.TryParse(ConfigurationManager.AppSettings["SourceFileNamesFilterType"].Trim(), out parsedFilter))
                    SourceFileNamesFilterType = parsedFilter;
            }

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["SourceFileExtensionsFilter"]))
                SourceFileExtensionsFilter = ProcessFilePathArguments(ConfigurationManager.AppSettings["SourceFileExtensionsFilter"].Trim(), PathType.Extension);

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["SourceFileExtensionsFilterType"]))
            {
                Filter parsedFilter;

                if (Enum.TryParse(ConfigurationManager.AppSettings["SourceFileExtensionsFilterType"].Trim(), out parsedFilter))
                    SourceFileExtensionsFilterType = parsedFilter;
            }

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["IncludedDomainNames"]))
                IncludedDomainNames = ProcessFilePathArguments(ConfigurationManager.AppSettings["IncludedDomainNames"].Trim());

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["DeleteOldFileVersions"]))
                DeleteOldFileVersions = ConfigurationManager.AppSettings["DeleteOldFileVersions"].Trim() == "1";

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["OutputFilePath"]))
                OutputFilePath = ConfigurationManager.AppSettings["OutputFilePath"].Trim();

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["VersionFilePath"]))
                VersionFilePath = ConfigurationManager.AppSettings["VersionFilePath"].Trim();

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["MD5HashPrefix"]))
                MD5HashPrefix = ConfigurationManager.AppSettings["MD5HashPrefix"].Trim();

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["NumberOfVersionsToKeep"]))
            {
                int parsedInt;

                if (int.TryParse(ConfigurationManager.AppSettings["NumberOfVersionsToKeep"], out parsedInt) && parsedInt > 0)
                    NumberOfVersionsToKeep = parsedInt;
            }
        }

        internal static void FindAssetAndSourceFiles()
        {
            FindAssetFiles();
            FindSourceFiles();
        }

        internal static void FindAssetFiles()
        {
            FileSearchData fileSearchData = new FileSearchData
            {
                FolderPaths = AssetFolderPaths,
                ExcludedFolderPaths = ExcludedAssetFolderPaths,
                FolderNamesFilter = AssetFolderNamesFilter,
                FolderNamesFilterType = AssetFolderNamesFilterType,
                FilePathsFilter = AssetFilePathsFilter,
                FilePathsFilterType = AssetFilePathsFilterType,
                FileExtensionsFilter = AssetFileExtensionsFilter,
                FileExtensionsFilterType = AssetFileExtensionsFilterType,
                FileNamesFilter = AssetFileNamesFilter,
                FileNamesFilterType = AssetFileNamesFilterType
            };

            Output.Write("**** Finding asset files.");
            AssetFiles = FileManager.FindFiles(fileSearchData, (Command != CommandType.Cleanup && Command != CommandType.CleanupButKeepNonVersioned));
        }

        internal static void FindSourceFiles()
        {
            FileSearchData fileSearchData = new FileSearchData
            {
                FolderPaths = SourceFolderPaths,
                ExcludedFolderPaths = ExcludedSourceFolderPaths,
                FolderNamesFilter = SourceFolderNamesFilter,
                FolderNamesFilterType = SourceFolderNamesFilterType,
                FilePathsFilter = SourceFilePathsFilter,
                FilePathsFilterType = SourceFilePathsFilterType,
                FileNamesFilter = SourceFileNamesFilter,
                FileNamesFilterType = SourceFileNamesFilterType,
                FileExtensionsFilter = SourceFileExtensionsFilter,
                FileExtensionsFilterType = SourceFileExtensionsFilterType
            };

            Output.Write("**** Finding source files.");
            SourceFiles = FileManager.FindFiles(fileSearchData);
        }

        internal static void CacheBust()
        {
            _assetFilesDeleted.Clear();
            _assetFilesRenamed.Clear();
            _sourceFilesUpdated.Clear();
            _stopwatch = Stopwatch.StartNew();
            VersionManager.VersionFilePath = VersionFilePath;

            Output.WriteLine();
            Output.WriteLine(string.Format("**** Run number: 1"));

            FindAssetFiles();
            HashAssets(AssetFiles);
            FindSourceFiles();
            UpdateSourceFiles();

            List<Asset> changedSourceAssetFiles = SourceFiles.Where(c => c.Changed && AssetFiles.Any(a => string.Equals(a.NewFilePath, c.NewFilePath, StringComparison.CurrentCultureIgnoreCase))).ToList();
            int runNumber = 1;
            const int maxRuns = 3;

            if (changedSourceAssetFiles.Count > 0)
                Output.WriteLine("**** Handling changed source files which are also assets");

            while (changedSourceAssetFiles.Count > 0)
            {
                runNumber++;

                if (runNumber > maxRuns)
                    break;

                Output.WriteLine(string.Format("**** Run number: {0}", runNumber));

                foreach (Asset asset in AssetFiles)
                    asset.Changed = false;

                foreach (Asset sourceFile in SourceFiles)
                {
                    if (!string.IsNullOrWhiteSpace(sourceFile.NewFilePath) && File.Exists(sourceFile.NewFilePath))
                    {
                        sourceFile.OriginalFilePath = sourceFile.NewFilePath;
                        sourceFile.OriginalHash = sourceFile.NewHash;
                    }

                    sourceFile.Changed = false;
                }

                foreach (Asset sourceFile in changedSourceAssetFiles)
                {
                    Asset asset = AssetFiles.First(c => string.Equals(c.NewFilePath, sourceFile.NewFilePath));

                    sourceFile.OriginalFilePath = sourceFile.NewFilePath;
                    sourceFile.OriginalHash = sourceFile.NewHash;
                    sourceFile.NewHash = FileManager.ComputeMd5Hash(sourceFile.OriginalFilePath);

                    if (CacheBustingType == CacheBustType.Hash)
                    {
                        sourceFile.NewFilePath = FileManager.GetNewFileName(sourceFile.OriginalFilePath, sourceFile.NewHash);
                    }
                    else
                    {
                        string filePath = VersionManager.GetFilePathWithoutVersionNumber(sourceFile.OriginalFilePath);
                        FileVersion fileVersion = VersionManager.FindFileVersion(filePath);

                        if (fileVersion == null)
                        {
                            fileVersion.FilePath = filePath;
                            fileVersion.Hash = sourceFile.OriginalHash;
                            fileVersion.VersionNumber = 1;
                        }

                        int? versionNumber = VersionManager.GetFileVersionNumber(sourceFile.OriginalFilePath);

                        if (!versionNumber.HasValue)
                            versionNumber = fileVersion.VersionNumber;

                        versionNumber++;
                        fileVersion.VersionNumber = versionNumber.Value;
                        sourceFile.NewFilePath = VersionManager.GetFilePathWithVersionNumber(filePath, versionNumber.Value);
                        VersionManager.UpdateFileVersion(fileVersion);
                    }

                    asset.OriginalFilePath = sourceFile.OriginalFilePath;
                    asset.OriginalHash = sourceFile.OriginalHash;
                    asset.NewHash = sourceFile.NewHash;
                    asset.NewFilePath = sourceFile.NewFilePath;
                    asset.Changed = true;
                }

                HashAssets(changedSourceAssetFiles);

                foreach (Asset sourceFile in changedSourceAssetFiles)
                {
                    sourceFile.Changed = false;

                    if (!string.IsNullOrWhiteSpace(sourceFile.NewFilePath) && File.Exists(sourceFile.NewFilePath))
                    {
                        sourceFile.OriginalFilePath = sourceFile.NewFilePath;
                        sourceFile.OriginalHash = sourceFile.NewHash;
                    }
                }

                UpdateSourceFiles();

                changedSourceAssetFiles = SourceFiles.Where(c => c.Changed && AssetFiles.Any(a => string.Equals(a.NewFilePath, c.NewFilePath, StringComparison.CurrentCultureIgnoreCase))).ToList();
            }


            _stopwatch.Stop();

            if (CacheBuster.CacheBustingType == CacheBuster.CacheBustType.Version)
                VersionManager.Save();

            Output.WriteLine("**** Cache busting is complete.");
            Output.WriteLine(string.Format("Total execution time in seconds: {0}", _stopwatch.Elapsed.TotalSeconds.ToString("N2")));
            Output.WriteLine(string.Format("Number of asset files renamed: {0}", _assetFilesRenamed.Count));
            Output.WriteLine(string.Format("Number of asset files deleted: {0}", _assetFilesDeleted.Count));
            Output.WriteLine(string.Format("Number of source files updated: {0}", _sourceFilesUpdated.Count));
        }

        internal static void AnalyzeUniqueFileExtensions()
        {
            FileSearchData fileSearchData = new FileSearchData
            {
                FolderPaths = AssetFolderPaths,
                ExcludedFolderPaths = ExcludedAssetFolderPaths,
                FolderNamesFilter = AssetFolderNamesFilter,
                FolderNamesFilterType = AssetFolderNamesFilterType,
                FilePathsFilter = AssetFilePathsFilter,
                FilePathsFilterType = AssetFilePathsFilterType,
                FileExtensionsFilter = AssetFileExtensionsFilter,
                FileExtensionsFilterType = AssetFileExtensionsFilterType,
                FileNamesFilter = AssetFileNamesFilter,
                FileNamesFilterType = AssetFileNamesFilterType
            };

            FileManager.FindUniqueFileExtensions(fileSearchData, "Asset folders unique extensions:");

            fileSearchData = new FileSearchData
            {
                FolderPaths = SourceFolderPaths,
                ExcludedFolderPaths = ExcludedSourceFolderPaths,
                FolderNamesFilter = SourceFolderNamesFilter,
                FolderNamesFilterType = SourceFolderNamesFilterType,
                FilePathsFilter = SourceFilePathsFilter,
                FilePathsFilterType = SourceFilePathsFilterType,
                FileNamesFilter = SourceFileNamesFilter,
                FileNamesFilterType = SourceFileNamesFilterType,
                FileExtensionsFilter = SourceFileExtensionsFilter,
                FileExtensionsFilterType = SourceFileExtensionsFilterType
            };

            FileManager.FindUniqueFileExtensions(fileSearchData, "Source folders unique extensions:");
        }

        internal static void CleanUp()
        {
            FindAssetFiles();

            int numberOfFilesDeleted = 0;
            Output.WriteLine("Cleaning up old asset versions.");

            foreach (Asset asset in AssetFiles)
            {
                asset.NewFilePath = asset.OriginalFilePath.ToLower().Trim();
                asset.OriginalFilePath = CacheBustingType == CacheBustType.Hash ? FileManager.GetFileNameWithoutHash(asset.OriginalFilePath).ToLower().Trim() 
                                                                                : VersionManager.GetFilePathWithoutVersionNumber(asset.OriginalFilePath).ToLower().Trim();

                if (string.Equals(asset.NewFilePath, asset.OriginalFilePath, StringComparison.CurrentCultureIgnoreCase) && Command == CommandType.CleanupButKeepNonVersioned)
                    asset.OriginalFilePath = string.Empty;
            }

            var groupedAssets = AssetFiles
                .Where(c => !string.IsNullOrWhiteSpace(c.OriginalFilePath))
                .GroupBy(c => c.OriginalFilePath)
                .Where(c => c.Count() > NumberOfVersionsToKeep)
                .ToList();

            foreach (var grouping in groupedAssets)
            {
                int fileCount = grouping.Count();

                if (Command == CommandType.CleanupButKeepNonVersioned && grouping.Count() > 0 && File.Exists(grouping.ToList()[0].OriginalFilePath))
                    fileCount++;

                Output.Write(string.Format("There are {0} versions for file: '{1}'.", fileCount, grouping.Key));

                if (Command == CommandType.Cleanup)
                    Output.WriteLine(string.Format(" Only {0} versions should be kept.", NumberOfVersionsToKeep));
                else
                    Output.WriteLine(string.Format(" {0} versions and the original version should be kept.", NumberOfVersionsToKeep));

                List<Asset> assetsToRemove = new List<Asset>();

                if (CacheBustingType == CacheBustType.Hash)
                {
                    assetsToRemove = grouping
                        .OrderBy(x => new FileInfo(x.NewFilePath).CreationTime)
                        .Select(x => x)
                        .Take(grouping.Count() - NumberOfVersionsToKeep)
                        .ToList();
                }
                else
                {
                    assetsToRemove = grouping
                        .OrderBy(x => VersionManager.GetFileVersionNumber(x.NewFilePath).HasValue ? VersionManager.GetFileVersionNumber(x.NewFilePath).Value : 0)
                        .Select(x => x)
                        .Take(grouping.Count() - NumberOfVersionsToKeep)
                        .ToList();
                }

                List<Asset> keptAssets = grouping.Where(x => !assetsToRemove.Any(c => string.Equals(x.NewFilePath, c.NewFilePath, StringComparison.CurrentCultureIgnoreCase))).ToList();

                foreach (Asset asset in assetsToRemove)
                {
                    try
                    {
                        Output.WriteLine(string.Format("Deleting asset file '{0}'.", asset.NewFilePath));
                        File.Delete(asset.NewFilePath);
                        numberOfFilesDeleted++;
                    }
                    catch (Exception ex)
                    {
                        Output.WriteLine(string.Format("An error occurred while trying to delete file '{0}'. {1}", asset.NewFilePath, ex.Message));
                    }
                }

                List<Asset> mergedAssets = new List<Asset>();
                mergedAssets.AddRange(assetsToRemove);
                mergedAssets.AddRange(keptAssets);

                if (Command == CommandType.CleanupButKeepNonVersioned && mergedAssets.Count > 0 && File.Exists(mergedAssets[0].OriginalFilePath) && !keptAssets.Any(c => string.Equals(mergedAssets[0].NewFilePath, c.NewFilePath, StringComparison.CurrentCultureIgnoreCase)))
                    keptAssets.Insert(0, new Asset { NewFilePath = mergedAssets[0].OriginalFilePath });

                foreach (Asset asset in keptAssets)
                    Output.WriteLine(string.Format("Kept asset file '{0}'.", asset.NewFilePath));
            }

            Output.WriteLine(string.Format("Number of old asset versions deleted: {0}.", numberOfFilesDeleted));
        }

        internal static List<string> ProcessFilePathArguments(string argument, PathType pathType = PathType.File)
        {
            List<string> paths = string.IsNullOrWhiteSpace(argument) ? new List<string>()
                                                       : argument.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries).ToList();

            if (pathType == PathType.Directory)
            {
                for (int i = 0; i < paths.Count; i++)
                    paths[i] = FileManager.GetDirectoryPath(paths[i]);
            }

            if (pathType == PathType.File || pathType == PathType.Directory)
            {
                for (int i = 0; i < paths.Count; i++)
                {
                    string path = paths[i];

                    string[] pathParts = path.Split(new[] { '\\', '/' });

                    if (pathParts.Length > 0 && (pathParts[0] == "." || pathParts[0] == ".."))
                    {
                        List<string> heirarchy = ApplicationPath.FullName.Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries).ToList();

                        foreach (string pathPart in pathParts)
                        {
                            if (pathPart == "." || string.IsNullOrWhiteSpace(pathPart))
                                continue;

                            if (pathPart == "..")
                            {
                                heirarchy.RemoveAt(heirarchy.Count - 1);
                                continue;
                            }

                            heirarchy.Add(pathPart);
                        }

                        path = string.Join(@"\", heirarchy);

                        if (pathType == PathType.Directory)
                            path = FileManager.GetDirectoryPath(path);
                    }

                    paths[i] = path;
                }
            }

            if (pathType == PathType.Extension)
            {
                for (int i = 0; i < paths.Count; i++)
                {
                    if (!paths[i].StartsWith("."))
                        paths[i] = "." + paths[i];
                }
            }

            return paths;
        }

        internal static List<string> ValidateArguments()
        {
            List<string> errors = new List<string>();

            if (string.IsNullOrWhiteSpace(SourceRootPath) && (Command != CommandType.Cleanup && Command != CommandType.CleanupButKeepNonVersioned))
                errors.Add("You must specify a source root path using the -srfp: argument.");

            if (AssetFolderPaths.Count == 0)
                errors.Add("You must specify at least one asset file path using the -afp: argument.");

            if (SourceFolderPaths.Count == 0 && (Command != CommandType.Cleanup && Command != CommandType.CleanupButKeepNonVersioned))
                errors.Add("You must specify at least one source file path using the -sfp: argument.");

            if (!string.IsNullOrWhiteSpace(AssetRootPath))
                errors.AddRange(ValidateFilePaths("-arfp:", new[] { AssetRootPath }));

            if (!string.IsNullOrWhiteSpace(SourceRootPath))
                errors.AddRange(ValidateFilePaths("-srfp:", new[] { SourceRootPath }));

            errors.AddRange(ValidateFilePaths("-afp:", AssetFolderPaths));
            errors.AddRange(ValidateFilePaths("-eafp:", ExcludedAssetFolderPaths));
            errors.AddRange(ValidateFilePaths("-sfp:", SourceFolderPaths));
            errors.AddRange(ValidateFilePaths("-esfp:", ExcludedSourceFolderPaths));

            if (!string.IsNullOrWhiteSpace(OutputFilePath))
                errors.AddRange(ValidateFilePaths("-o:", new[] { OutputFilePath }));

            if (CacheBustingType == CacheBustType.Version && (Command != CommandType.Cleanup && Command != CommandType.CleanupButKeepNonVersioned))
            {
                if (string.IsNullOrWhiteSpace(VersionFilePath))
                    errors.Add("You must specify the version file path using the -v: argument.");

                if (!string.IsNullOrWhiteSpace(VersionFilePath))
                    errors.AddRange(ValidateFilePaths("-v:", new[] { VersionFilePath }));
            }

            return errors;
        }

        internal static IEnumerable<string> ValidateFilePaths(string argument, IEnumerable<string> filePaths)
        {
            return filePaths.Where(c => !Directory.Exists(c.Contains(@"\") ? c.Substring(0, c.LastIndexOf(@"\") + 1) : c))
                .Select(filePath => string.Format("The file path '{0}' used for command line argument {1} does not exist.", filePath, argument));
        }

        internal static IEnumerable<string> ValidateSourceFilePaths(string argument, IEnumerable<string> filePaths)
        {
            if (string.IsNullOrWhiteSpace(SourceRootPath))
                return new string[] { };

            return filePaths.Where(c => !c.StartsWith(SourceRootPath))
                .Select(filePath => string.Format("The file path '{0}' used for command line argument {1} is not a child of the source root path -sfp.", filePath, argument));
        }

        internal static void HashAssets(List<Asset> assets)
        {
            Output.WriteLine("**** Hashing asset file names.");

            foreach (Asset asset in assets)
            {
                if (string.IsNullOrWhiteSpace(asset.NewHash) || string.IsNullOrWhiteSpace(asset.NewFilePath) ||
                    (string.Equals(asset.OriginalFilePath, asset.NewFilePath, StringComparison.CurrentCultureIgnoreCase)))
                {
                    continue;
                }

                if (File.Exists(asset.NewFilePath))
                    continue;

                File.Copy(asset.OriginalFilePath, asset.NewFilePath);
                Output.WriteLine(string.Format("Renaming file '{0}' to '{1}'.", asset.OriginalFilePath, asset.NewFilePath));
                asset.Changed = true;

                if (!_assetFilesRenamed.Any(c => string.Equals(c, CacheBustingType == CacheBustType.Hash ? FileManager.GetFileNameWithoutHash(asset.NewFilePath) : VersionManager.GetFilePathWithoutVersionNumber(asset.NewFilePath), StringComparison.CurrentCultureIgnoreCase)))
                    _assetFilesRenamed.Add(CacheBustingType == CacheBustType.Hash ? FileManager.GetFileNameWithoutHash(asset.NewFilePath) : VersionManager.GetFilePathWithoutVersionNumber(asset.NewFilePath));

                if (!DeleteOldFileVersions)
                    continue;

                if (!_assetFilesDeleted.Any(c => string.Equals(c, CacheBustingType == CacheBustType.Hash ? FileManager.GetFileNameWithoutHash(asset.NewFilePath) : VersionManager.GetFilePathWithoutVersionNumber(asset.NewFilePath), StringComparison.CurrentCultureIgnoreCase)))
                    _assetFilesDeleted.Add(CacheBustingType == CacheBustType.Hash ? FileManager.GetFileNameWithoutHash(asset.NewFilePath) : VersionManager.GetFilePathWithoutVersionNumber(asset.NewFilePath));

                File.Delete(asset.OriginalFilePath);
                Output.WriteLine(string.Format("Deleting file '{0}'.", asset.OriginalFilePath));
            }
        }

        internal static void UpdateSourceFiles()
        {
            Output.WriteLine("**** Updating source files.");
            int counter = 0;

            foreach (Asset sourceFile in SourceFiles)
            {
                counter++;
                bool readOldFilePath = true;

                if (AssetFiles.Any(c => c.Changed && string.Equals(c.NewFilePath, sourceFile.OriginalFilePath, StringComparison.CurrentCultureIgnoreCase)))
                {
                    var assetFile = AssetFiles.First(c => string.Equals(c.NewFilePath, sourceFile.OriginalFilePath, StringComparison.CurrentCultureIgnoreCase));
                    sourceFile.OriginalFilePath = assetFile.OriginalFilePath;
                    sourceFile.NewFilePath = assetFile.NewFilePath;
                    readOldFilePath = false;
                }

                Output.WriteLine(string.Format("Checking source file '{0}'.", readOldFilePath ? sourceFile.OriginalFilePath : sourceFile.NewFilePath));

                string originalFileContent = File.ReadAllText(readOldFilePath ? sourceFile.OriginalFilePath : sourceFile.NewFilePath);
                string fileContent = originalFileContent;

                foreach (Asset asset in AssetFiles.Where(c => c.Changed))
                {
                    if (string.Equals(asset.OriginalFilePath, asset.NewFilePath, StringComparison.CurrentCultureIgnoreCase))
                        continue;

                    FileInfo originalFileInfo = new FileInfo(asset.OriginalFilePath);
                    int index = fileContent.IndexOf(originalFileInfo.Name, StringComparison.CurrentCultureIgnoreCase);

                    while (index != -1)
                    {
                        int originalFileNameLength = originalFileInfo.Name.Length;
                        string pathBefore = GetPathBefore(fileContent, index - 1);
                        string pathAfter = GetPathAfter(fileContent, index + originalFileNameLength);
                        string originalPathBefore = pathBefore;

                        if (!CheckHost(ref pathBefore))
                        {
                            index = fileContent.IndexOf(originalFileInfo.Name, index + 1, StringComparison.CurrentCultureIgnoreCase);
                            continue;
                        }

                        // add path after to file name and compare that they match
                        string fileName = (originalFileInfo.Name + pathAfter).Trim();

                        if (!string.Equals(originalFileInfo.Name, fileName, StringComparison.CurrentCultureIgnoreCase))
                        {
                            index = fileContent.IndexOf(originalFileInfo.Name, index + 1, StringComparison.CurrentCultureIgnoreCase);
                            continue;
                        }

                        string originalFilePath = sourceFile.OriginalFilePath;
                        bool absolute = false;

                        if (pathBefore.StartsWith("/") || pathBefore.StartsWith("~/"))
                        {
                            originalFilePath = SourceRootPath;
                            absolute = true;
                        }

                        bool isValid = IsPathValid(pathBefore, originalFilePath, asset, sourceFile);

                        if (!isValid)
                        {
                            if (!absolute)
                            {
                                while (pathBefore.StartsWith("../") || pathBefore.StartsWith("./"))
                                    pathBefore = pathBefore.Substring(pathBefore.IndexOf("/") + 1);
                            }
                            string tempOriginalFilePath = SourceRootPath + originalFilePath.Substring(originalFilePath.LastIndexOf(@"\") + 1);
                            isValid = IsPathValid(pathBefore, tempOriginalFilePath, asset, sourceFile);
                        }

                        if (!isValid)
                        {
                            index = fileContent.IndexOf(originalFileInfo.Name, index + 1, StringComparison.CurrentCultureIgnoreCase);
                            continue;
                        }

                        FileInfo fileInfo = new FileInfo(asset.NewFilePath);
                        string start = fileContent.Substring(0, index);
                        string end = fileContent.Substring(index + originalFileInfo.Name.Length);
                        fileContent = start + fileInfo.Name + end;
                        Output.WriteLine(string.Format(" - Replacing '{0}' with '{1}'.", originalPathBefore + fileName, originalPathBefore + fileInfo.Name));

                        index = fileContent.IndexOf(originalFileInfo.Name, index + 1, StringComparison.CurrentCultureIgnoreCase);
                    }
                }

                if (string.Equals(fileContent, originalFileContent))
                    continue;

                string filePath;
                if (!string.IsNullOrWhiteSpace(sourceFile.NewFilePath))
                {
                    File.WriteAllText(sourceFile.NewFilePath, fileContent, Encoding.UTF8);
                    filePath = sourceFile.NewFilePath;
                }
                else
                {
                    File.WriteAllText(sourceFile.OriginalFilePath, fileContent, Encoding.UTF8);
                    filePath = sourceFile.OriginalFilePath;
                }

                Output.WriteLine(string.Format(" - Updating source file '{0}'.", !string.IsNullOrWhiteSpace(sourceFile.NewFilePath) ? sourceFile.NewFilePath : sourceFile.OriginalFilePath));

                if (!_sourceFilesUpdated.Any(c => string.Equals(c, CacheBustingType == CacheBustType.Hash ? FileManager.GetFileNameWithoutHash(filePath) : VersionManager.GetFilePathWithoutVersionNumber(filePath), StringComparison.CurrentCultureIgnoreCase)))
                    _sourceFilesUpdated.Add(CacheBustingType == CacheBustType.Hash ? FileManager.GetFileNameWithoutHash(filePath) : VersionManager.GetFilePathWithoutVersionNumber(filePath));

                sourceFile.Changed = true;
            }
        }

        private static string GetPathBefore(string fileContent, int startIndex)
        {
            char[] prefixCharacters = new[] { '<', '>', '{', '"', '\'', '(', ' ', ',', ':', ';', '|', '\r', '\n' };
            string pathBefore = string.Empty;

            while (startIndex >= 0)
            {
                char currentCharacter = fileContent[startIndex];

                if (prefixCharacters.Any(c => c == currentCharacter))
                    break;

                pathBefore = currentCharacter + pathBefore;
                startIndex--;
            }

            return pathBefore;
        }

        private static string GetPathAfter(string fileContent, int startIndex)
        {
            char[] suffixCharacters = new[] { '<', '>', '}', '"', '\'', ')', ';', ' ', ',', '?', '\r', '\n', '|' };
            string pathAfter = string.Empty;

            while (startIndex < fileContent.Length)
            {
                char currentCharacter = fileContent[startIndex];

                if (suffixCharacters.Any(c => c == currentCharacter))
                    break;

                pathAfter += currentCharacter;
                startIndex++;
            }

            return pathAfter;
        }

        private static bool CheckHost(ref string pathBefore)
        {
            string host = string.Empty;

            if (pathBefore.StartsWith("http://", StringComparison.OrdinalIgnoreCase))
                host = pathBefore.Substring(7);

            if (pathBefore.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
                host = pathBefore.Substring(8);

            if (pathBefore.StartsWith("//", StringComparison.OrdinalIgnoreCase))
                host = pathBefore.Substring(2);

            if (!string.IsNullOrWhiteSpace(host) && host.Contains("/"))
            {
                host = host.Substring(0, host.IndexOf("/"));

                if (!IncludedDomainNames.Any(c => string.Equals(c, host, StringComparison.CurrentCultureIgnoreCase)))
                    return false;

                pathBefore = pathBefore.Substring(pathBefore.IndexOf(host) + host.Length);
            }

            return true;
        }

        private static bool IsPathValid(string pathBefore, string originalFilePath, Asset asset, Asset sourceFile)
        {
            List<string> entryHeirarchy = pathBefore.Split(new[] { "/", @"\" }, StringSplitOptions.RemoveEmptyEntries).ToList();
            List<string> sourceFileHeirarchy = originalFilePath.Substring(0, originalFilePath.LastIndexOf(@"\")).Split(new[] { "/", @"\" }, StringSplitOptions.RemoveEmptyEntries).ToList();

            if (entryHeirarchy.Count > 0 && entryHeirarchy.First() == "~")
                entryHeirarchy.RemoveAt(0);

            bool pathValid;

            if (entryHeirarchy.Count == 0)
                pathValid = string.Equals(FileManager.GetDirectoryPath(asset.DirectoryPath), FileManager.GetDirectoryPath(sourceFile.DirectoryPath), StringComparison.CurrentCultureIgnoreCase);
            else
            {
                foreach (string directory in entryHeirarchy)
                {
                    if (directory == "..")
                    {
                        sourceFileHeirarchy.RemoveAt(sourceFileHeirarchy.Count - 1);
                        continue;
                    }

                    if (directory == ".")
                    {
                        continue;
                    }

                    sourceFileHeirarchy.Add(directory);
                }

                string requestedFilePath = string.Join("\\", sourceFileHeirarchy);
                pathValid = string.Equals(FileManager.GetDirectoryPath(asset.DirectoryPath), FileManager.GetDirectoryPath(requestedFilePath), StringComparison.CurrentCultureIgnoreCase);
            }

            return pathValid;
        }

        #endregion Internal/Private methods
    }
}
